﻿// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.AttributedModel;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Reflection;
using Microsoft.Internal;
using Microsoft.Internal.Collections;

namespace System.ComponentModel.Composition.Hosting
{
    public static class CompositionServices
    {
        private const string CompositionNamespace = "System.ComponentModel.Composition";
        
        public const string AdapterContractName = CompositionNamespace + ".AdapterContract";
        public const string AdapterFromContractMetadataName = "FromContract";
        public const string AdapterToContractMetadataName = "ToContract";
        public const string PartCreationPolicyMetadataName = CompositionNamespace + ".CreationPolicy";

        private static readonly string[] blockedPartMetadataNames = new string[]
        {
            CompositionServices.PartCreationPolicyMetadataName
        };

        public static string GetContractName(Type type)
        {
            Requires.NotNull(type, "type");
            
            return GetContractNameFromContractTypeAttribute(type) ?? ContractNameServices.GetDefaultContractName(type);
        }

        internal static AttributedComposablePartDefinition CreateAttributedPartDefinition(Type attributedType, ICompositionElement origin)
        {
            return new AttributedComposablePartDefinition(attributedType, origin);
        }

        public static ComposablePart CreateAttributedPart(object attributedPart)
        {
            return new AttributedComposablePart(attributedPart);
        }

        internal static Type GetDefaultTypeFromMember(this MemberInfo member)
        {
            Assumes.NotNull(member);

            switch (member.MemberType)
            {
                case MemberTypes.Property:
                    return ((PropertyInfo)member).PropertyType;

                case MemberTypes.NestedType:
                case MemberTypes.TypeInfo:
                    return ((Type)member);

                case MemberTypes.Field:
                default:
                    Assumes.IsTrue(member.MemberType == MemberTypes.Field);
                    return ((FieldInfo)member).FieldType;
            }
        }

        internal static string GetContractNameFromExport(this MemberInfo member, ExportAttribute export)
        {
            if (!string.IsNullOrEmpty(export.ContractName))
                return export.ContractName;

            Assumes.IsTrue(member.MemberType == MemberTypes.Field
                || member.MemberType == MemberTypes.Property
                || member.MemberType == MemberTypes.Method
                || member.MemberType == MemberTypes.TypeInfo
                || member.MemberType == MemberTypes.NestedType);

            if (member.MemberType == MemberTypes.Method)
            {
                throw new InvalidOperationException(Strings.MustProvideExportNameWhenExportingMethods);
            }

            return GetContractName(member.GetDefaultTypeFromMember(), member);
        }

        internal static string GetContractNameFromImport(ImportAttribute import, Type importType)
        {
            if (!string.IsNullOrEmpty(import.ContractName))
            {
                return import.ContractName;
            }

            Type itemType;
            if (CollectionServices.IsEnumerableType(importType, out itemType))
            {
                importType = itemType;
            }

            if (importType.IsGenericType && ExportServices.IsKnownExportType(importType))
            {
                importType = importType.GetGenericArguments()[0];
            }

            return GetContractName(importType);
        }

        internal static IDictionary<string, object> GetPartMetadataForType(this Type type, CompositionOptionsAttribute compositionOptions)
        {
            IDictionary<string, object> dictionary = new Dictionary<string, object>(StringComparers.MetadataKeyNames);

            if (compositionOptions.CreationPolicy != CreationPolicy.Any)
            {
                dictionary.Add(PartCreationPolicyMetadataName, compositionOptions.CreationPolicy);
            }

            foreach (PartMetadataAttribute partMetadata in type.GetAttributes<PartMetadataAttribute>(false))
            {
                if (blockedPartMetadataNames.Contains(partMetadata.Name, StringComparers.MetadataKeyNames) 
                    || dictionary.ContainsKey(partMetadata.Name))
                {
                    // Perhaps we should log an error here so that people know this value is being ignored.
                    continue;
                }

                dictionary.Add(partMetadata.Name, partMetadata.Value);
            }

            if (dictionary.Count == 0)
            {
                return MetadataServices.EmptyMetadata;
            }
            else
            {
                return dictionary.AsReadOnly();
            }
        }

        internal static void TryExportMetadataForMember(this MemberInfo member, out IDictionary<string, object> dictionary)
        {
            List<string> invalidMetadataNames = new List<string>();
            invalidMetadataNames.Add(CompositionServices.PartCreationPolicyMetadataName);

            dictionary = new Dictionary<string, object>();

            foreach (var attr in member.GetAttributes<Attribute>())
            {
                var provider = attr as ExportMetadataAttribute;

                if (provider != null)
                {
                    if (invalidMetadataNames.Contains(provider.Name))
                    {
                        continue;
                    }

                    if (!dictionary.TryContributeMetadataValue(provider.Name, provider.Value, provider.IsMultiple))
                    {
                        invalidMetadataNames.Add(provider.Name);
                    }
                }
                else if (attr.GetType().IsAttributeDefined<MetadataAttributeAttribute>())
                {
                    bool allowsMultiple = false;
                    AttributeUsageAttribute usage = attr.GetType().GetFirstAttribute<AttributeUsageAttribute>();

                    if (usage != null)
                    {
                        allowsMultiple = usage.AllowMultiple;
                    }

                    foreach (PropertyInfo pi in attr.GetType().GetProperties())
                    {
                        if (pi.DeclaringType != typeof(Attribute))
                        {
                            if (invalidMetadataNames.Contains(pi.Name))
                            {
                                continue;
                            }

                            object value = pi.GetValue(attr, null);
                            if (!dictionary.TryContributeMetadataValue(pi.Name, value, allowsMultiple))
                            {
                                invalidMetadataNames.Add(pi.Name);
                            }
                        }
                    }
                }
            }

            List<TempTuple<string, object>> lists = new List<TempTuple<string, object>>();
            foreach (var element in dictionary)
            {
                object list = element.Value;
                if (list is IList)
                {
                    lists.Add(new TempTuple<string, object>(element.Key, element.Value));
                }
            }

            foreach (var element in lists)
            {
                object list = element.Second;
                dictionary.Remove(element.First);
                dictionary.Add(element.First, list.GetType().GetMethod("ToArray").Invoke(list, null));
            }

            return;
        }

        private static bool TryContributeMetadataValue(this IDictionary<string, object> dictionary, string name, object value, bool allowsMultiple)
        {
            object metadataValue;
            if (!dictionary.TryGetValue(name, out metadataValue))
            {
                if (allowsMultiple)
                {
                    IList list = CollectionServices.CreateGenericList(value.GetType());
                    list.Add(value);
                    value = list;
                }

                dictionary.Add(name, value);
            }
            else
            {
                IList list = metadataValue as IList;
                if (!allowsMultiple || list == null)
                {
                    // Either single value already found when should be multiple
                    // or a duplicate name already exists
                    dictionary.Remove(name);
                    return false;
                }

                try
                {
                    list.Add(value);
                }
                catch(ArgumentException)
                {
                    // The metadata is not the same type as the underlying generic collection
                    // make it a List<object>
                    List<object> replacementList = new List<object>(list.Cast<object>());
                    replacementList.Add(value);
                    dictionary.Remove(name);
                    dictionary.Add(name, replacementList);
                }
            }
            return true;
        }

        //UNDONE: Need to add these warnings somewhere...Dev10:472538 should address this.
        //internal static CompositionResult MatchRequiredMetadata(this IDictionary<string, object> metadata, IEnumerable<string> requiredMetadata, string contractName)
        //{
        //    Assumes.IsTrue(metadata != null);

        //    var result = CompositionResult.SucceededResult;

        //    var missingMetadata = (requiredMetadata == null) ? null : requiredMetadata.Except<string>(metadata.Keys);
        //    if (missingMetadata != null && missingMetadata.Any())
        //    {
        //        result = result.MergeIssue(
        //            CompositionError.CreateIssueAsWarning(CompositionErrorId.RequiredMetadataNotFound,
        //            Strings.RequiredMetadataNotFound,
        //            contractName,
        //            string.Join(", ", missingMetadata.ToArray())));

        //        return new CompositionResult(false, result.Issues);
        //    }

        //    return result;
        //}

        internal static IEnumerable<string> GetRequiredMetadata(Type metadataViewType)
        {
            Assumes.NotNull(metadataViewType);

            if (ExportServices.IsDefaultMetadataViewType(metadataViewType)
            ||  ExportServices.IsDictionaryConstructorViewType(metadataViewType)
            || !metadataViewType.IsInterface)
            {
                return Enumerable.Empty<string>();
            }

            // A metadata view is required to be an Intrerface, and therefore only properties are allowed
            IEnumerable<string> requiredMetadata = from property in metadataViewType.GetAllProperties()
                                                   where (property.GetFirstAttribute<DefaultValueAttribute>() == null)
                                                   select property.Name;
            return requiredMetadata;
        }

        private static string GetContractNameFromContractTypeAttribute(Type type)
        {
            string name = null;
            var contractTypeAttribute = type.GetFirstAttribute<ContractTypeAttribute>();
            if (contractTypeAttribute != null)
            {
                name = contractTypeAttribute.ContractName.Length == 0 ? ContractNameServices.GetDefaultContractName(type) : contractTypeAttribute.ContractName;
            }
            return name;
        }

        private static string GetContractName(Type type, MemberInfo member)
        {
            string name = GetContractNameFromContractTypeAttribute(type);
            if (name == null)
            {
                name = ContractNameServices.GetDefaultContractName(type, member);
            }
            return name;
        }

        internal static object GetExportedObjectFromComposedPart(CompositionEngine engine, ComposablePart part, ExportDefinition definition)
        {
            try
            {
                engine.SatisfyImports(part, true);
            }
            catch (CompositionException ex)
            {
                throw ExceptionBuilder.CreateCannotGetExportedObject(part, definition, ex);
            }

            return GetExportedObjectWrappingPartException(part, definition);
        }

        private static object GetExportedObjectWrappingPartException(ComposablePart part, ExportDefinition definition)
        {
            try
            {
                return part.GetExportedObject(definition);
            }
            catch (ComposablePartException ex)
            {
                throw ExceptionBuilder.CreateCannotGetExportedObject(part, definition, ex);
            }
        }
        
        internal static bool IsRecomposable(this ComposablePart part)
        {
            return part.ImportDefinitions.Any(import => import.IsRecomposable);
        }

        internal static CreationPolicy GetCreationPolicy(this IDictionary<string, object> metadata)
        {
            Assumes.NotNull(metadata);
            object untypedPolicy;
            if (!metadata.TryGetValue(CompositionServices.PartCreationPolicyMetadataName, out untypedPolicy) ||
                !(untypedPolicy is CreationPolicy))
            {
                return CreationPolicy.Any;
            }

            return (CreationPolicy)untypedPolicy;
        }

        internal static CompositionResult<T> TryInvoke<T>(Func<T> action)
        {
            try
            {
                T value = action();
                return new CompositionResult<T>(value);
            }
            catch (CompositionException ex)
            {
                return new CompositionResult<T>(ex.Errors);
            }
        }

        internal static CompositionResult TryInvoke(Action action)
        {
            try
            {
                action();
                return CompositionResult.SucceededResult;
            }
            catch (CompositionException ex)
            {
                return new CompositionResult(ex.Errors);
            }
        }

        internal static CompositionResult TryFire<TEventArgs>(EventHandler<TEventArgs> _delegate, object sender, TEventArgs e)
            where TEventArgs : EventArgs
        {
            CompositionResult result = CompositionResult.SucceededResult;
            foreach (EventHandler<TEventArgs> _subscriber in _delegate.GetInvocationList())
            {
                try
                {
                    _subscriber.Invoke(sender, e);
                }
                catch (CompositionException ex)
                {
                    result = result.MergeErrors(ex.Errors);
                }
            }

            return result;
        }

        internal static Func<ExportDefinition, bool> GetConstraint(this ImportDefinition definition)
        {
            ContractBasedImportDefinition contractDefinition = definition as ContractBasedImportDefinition;

            if (contractDefinition != null)
            {
                return contractDefinition.MatchConstraint;
            }
            
            return definition.Constraint.Compile();
        }

        internal static CreationPolicy GetRequiredCreationPolicy(this ImportDefinition definition)
        {
            ContractBasedImportDefinition contractDefinition = definition as ContractBasedImportDefinition;

            if (contractDefinition != null)
            {
                return contractDefinition.RequiredCreationPolicy;
            }

            return CreationPolicy.Any;
        }

        internal static bool MatchConstraint(this ContractBasedImportDefinition contractDefinition, ExportDefinition exportDefinition)
        {
            if (!StringComparers.ContractName.Equals(contractDefinition.ContractName, exportDefinition.ContractName))
            {
                return false;
            }

            return MatchRequiredMatadata(contractDefinition, exportDefinition);
        }

        private static bool MatchRequiredMatadata(this ContractBasedImportDefinition contractDefinition, ExportDefinition definition)
        {
            IDictionary<string, object> exportMetadata = definition.Metadata;

            foreach (string metadataKey in contractDefinition.RequiredMetadata)
            {
                if (!exportMetadata.ContainsKey(metadataKey))
                {
                    return false;
                }
            }

            if (contractDefinition.RequiredCreationPolicy == CreationPolicy.Any)
            {
                return true;
            }

            CreationPolicy exportPolicy = exportMetadata.GetCreationPolicy();
            return exportPolicy == CreationPolicy.Any ||
                   exportPolicy == contractDefinition.RequiredCreationPolicy;
        }

        /// <summary>
        ///     Returns a value indicating whether cardinality is 
        ///     <see cref="ImportCardinality.ZeroOrOne"/> or 
        ///     <see cref="ImportCardinality.ExactlyOne"/>.
        /// </summary>
        internal static bool IsAtMostOne(this ImportCardinality cardinality)
        {
            return cardinality == ImportCardinality.ZeroOrOne || cardinality == ImportCardinality.ExactlyOne;
        }
    }
}
